package policy;

import java.util.ArrayList;

import policy.branches.ActionBranch;
import policy.branches.Branch;
import policy.branches.BranchFactory;
import policy.nodes.Node;

/**
 * This class represents a firewall policy using a common 5-tuple filtering
 * system. If you wish to extend to more than the standard
 * protocol,src_ip,src_port,dest_ip,dest_port system, you must create the new
 * Branch types by extending the Branch class and create a new BranchFactory by
 * extending the BranchFactory class.
 * 
 * @author sscull
 * 
 */
public class Policy {
	public final static int NOANOMALY = 0;
	public final static int GENERALIZATION = 1;
	public final static int SPECIALIZATION = 2;
	public final static int SHADOWING = 3;
	public final static int REDUNDANCY = 4;
	public final static int CORRELATION = 5;

	public static String[] FIELDS = { "protocol", "src_ip", "src_port",
			"dest_ip", "dest_port", "action" };

	private Node root;
	private BranchFactory factory = new BranchFactory();

	public Policy(ArrayList<String[]> rules) {
		root = new Node(FIELDS[0]);
		for (int i = 0; i < rules.size(); i++)
			buildTree(i, 0, root, 0, true, rules.get(i));
	}

	/**
	 * Customize fields and BranchFactory from the standard
	 * protocol,src_ip,src_port,dest_ip,dest_port Make sure that the root of
	 * your tree is the first field and the action is the last.
	 */
	public Policy(ArrayList<String[]> rules, String[] fields,
			BranchFactory factory) {
		FIELDS = fields;
		this.factory = factory;
		root = new Node(FIELDS[0]);
		for (int i = 0; i < rules.size(); i++)
			buildTree(i, 0, root, 0, true, rules.get(i));
	}

	/**
	 * @return the root of the policy tree, usually a ProtocolNode.
	 */
	public Node getRoot() {
		return root;
	}

	/**
	 * Builds the tree and checks for anomalies.
	 * 
	 * @param rule
	 *            the rule added to the tree
	 * @param field
	 *            the field we're currently looking t
	 * @param node
	 *            the current node in the tree that corresponds to the field
	 * @param anomalyState
	 *            the current state of the anomaly
	 * @param add
	 *            whether or not we're checking for an anomaly or actually
	 *            adding a new branch
	 * @param r
	 *            the values of the fields of the current rule
	 */
	public void buildTree(int rule, int field, Node node, int anomalyState,
			boolean add, String[] r) {
		if (!FIELDS[field].equals(FIELDS[FIELDS.length - 1])) {
			boolean valueFound = false;
			ArrayList<Branch> branches = node.getBranches();
			Branch temp = factory.getBranch(field, r[field], rule);
			for (Branch b : branches) {
				if (b.getValue().equals(r[field])) {
					valueFound = true;
					if (anomalyState == Policy.NOANOMALY)
						buildTree(rule, field + 1, b.getNode(),
								Policy.REDUNDANCY, add, r);
					else
						buildTree(rule, field + 1, b.getNode(), anomalyState,
								add, r);
				} else if (temp.compareTo(b) > 0) {
					if (anomalyState == Policy.SHADOWING)
						buildTree(rule, field + 1, b.getNode(),
								Policy.CORRELATION, false, r);
					else
						buildTree(rule, field + 1, b.getNode(),
								Policy.GENERALIZATION, false, r);
				} else if (temp.compareTo(b) < 0) {
					if (anomalyState == Policy.GENERALIZATION)
						buildTree(rule, field + 1, b.getNode(),
								Policy.CORRELATION, false, r);
					else
						buildTree(rule, field + 1, b.getNode(),
								Policy.SHADOWING, false, r);
				}
			}
			if (!valueFound && add) {
				Branch b = factory.getBranch(field, r[field], rule);
				node.addBranch(b);
				buildTree(rule, field + 1, b.getNode(), Policy.NOANOMALY, true,
						r);
			}
		} else {
			if (add)
				node.addBranch(factory.getBranch(field, r[field], rule));
			decideAnomaly(rule, field, node, anomalyState, r);
		}
	}

	private String decideAnomaly(int rule, int field, Node node,
			int anomalyState, String[] r) {
		ArrayList<Branch> branches;
		String anom = null;
		if ((branches = node.getBranches()) != null) {
			ActionBranch b = (ActionBranch) branches.get(0);
			if (rule <= b.getRule())
				return anom;
			if (anomalyState == Policy.CORRELATION) {
				if (!(r[FIELDS.length - 1].equals(b.getValue()))) {
					b.setAnomaly(Policy.CORRELATION);
					anom = "Rule " + (rule + 1)
							+ " is in correlation with rule "
							+ (b.getRule() + 1);
					System.err.println(anom);
				}
			} else if (anomalyState == Policy.GENERALIZATION
					&& !(r[FIELDS.length - 1].equals(b.getValue()))) {
				b.setAnomaly(Policy.NOANOMALY);
				anom = "Rule " + (rule + 1) + " is a generalization of rule "
						+ (b.getRule() + 1);
				System.err.println(anom);
			} else if (anomalyState == Policy.GENERALIZATION
					&& r[FIELDS.length - 1].equals(b.getValue())) {
				if (b.getAnomaly() == Policy.NOANOMALY) {
					b.setAnomaly(Policy.REDUNDANCY);
					anom = "Rule " + (b.getRule() + 1)
							+ " is redundant to rule " + (rule + 1);
					System.err.println(anom);
				}
			} else if (r[FIELDS.length - 1].equals(b.getValue())) {
				b.setAnomaly(Policy.REDUNDANCY);
				anom = "Rule " + (rule + 1) + " is redundant to rule "
						+ (b.getRule() + 1);
				System.err.println(anom);
			} else if (!(r[FIELDS.length - 1].equals(b.getValue()))) {
				b.setAnomaly(Policy.SHADOWING);
				anom = "Rule " + (rule + 1) + " is shadowed by rule "
						+ (b.getRule() + 1);
				System.err.println(anom);
			}
		}
		return null;
	}

	/**
	 * Searches a tree for the action taken against a packet using a depth-first
	 * search.
	 * 
	 * @param packet
	 *            the log item to be searched against.
	 * @param field
	 *            the current field of the packet
	 * @param n
	 *            the node of the current level we are on.
	 * @return if the log item agrees with what the firewall states, true,
	 *         otherwise false
	 */
	public boolean searchPolicy(String[] packet, int field, Node n) {
		ArrayList<Branch> b = n.getBranches();
		if (field == FIELDS.length - 1) {
			return b.get(0).getValue().equals(packet[field]);
		}
		boolean found = false;
		for (int i = 0; i < b.size(); i++) {
			if (b.get(i).getValue().equals(packet[field]))
				found = searchPolicy(packet, field + 1, b.get(i).getNode());
			else {
				Branch t = factory.getBranch(field, packet[field], 0);
				if (t.compareTo(b.get(i)) < 1) {
					found = searchPolicy(packet, field + 1, b.get(i).getNode());
				}
			}
		}
		return found;
	}
}